﻿using System.Collections;
using System.Collections.Generic;

namespace Devil
{
    /// <summary>
    /// 作为 RandomList 的补充
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CascadeList<T> : ICollection<T>
    {
        T[] mMainDatas;
        T[] mTempDatas;
        int mSize;
        EquatableDelegate<T> mEqual;
        bool mIsValue;

        public CascadeList()
        {
            mMainDatas = new T[64];
            mEqual = IsEqual;
            mIsValue = typeof(T).IsValueType;
        }

        public CascadeList(int mainCapacity)
        {
            mMainDatas = new T[mainCapacity];
            mEqual = IsEqual;
            mIsValue = typeof(T).IsValueType;
        }

        public CascadeList(int mainCapacity, EquatableDelegate<T> equality)
        {
            mIsValue = typeof(T).IsValueType;
            mMainDatas = new T[mainCapacity];
            if (equality == null)
                mEqual = IsEqual;
            else
                mEqual = equality;
        }

        bool IsEqual(T a, T b)
        {
            if (a == null)
                return b == null;
            return a.Equals(b);
        }

        public T this[int index]
        {
            get
            {
                if (index < 0 || index >= mSize)
                    throw new System.IndexOutOfRangeException();
                else if (index < mMainDatas.Length)
                    return mMainDatas[index];
                else
                    return mTempDatas[index - mMainDatas.Length];
            }
            set
            {
                if (index < 0 || index >= mSize)
                    throw new System.IndexOutOfRangeException();
                if (mSize < mMainDatas.Length)
                {
                    mMainDatas[mSize] = value;
                }
                else
                {
                    var size = mSize - mMainDatas.Length;
                    mTempDatas[size] = value;
                }
            }
        }

        public int Count { get { return mSize; } }

        public bool IsReadOnly { get { return false; } }

        public void Add(T item)
        {
            if (mSize < mMainDatas.Length)
            {
                mMainDatas[mSize++] = item;
            }
            else
            {
                var size = mSize - mMainDatas.Length;
                ParallelUtils.EnsureNativeSize(ref mTempDatas, size, size + 5);
                mTempDatas[size] = item;
                mSize++;
            }
        }

        public bool Remove(T item)
        {
            var i = IndexOf(item);
            if (i != -1)
            {
                RemoveAt(i);
                return true;
            }
            return false;
        }

        public T RemoveAt(int index)
        {
            if (index < 0 || index >= mSize)
                throw new System.IndexOutOfRangeException();
            mSize--;
            T result;
            if (index < mMainDatas.Length)
            {
                result = mMainDatas[index];
                if (mSize < mMainDatas.Length)
                {
                    mMainDatas[index] = mMainDatas[mSize];
                    mMainDatas[mSize] = default;
                }
                else
                {
                    var t = mSize - mMainDatas.Length;
                    mMainDatas[index] = mTempDatas[t];
                    if (mSize > mMainDatas.Length)
                        mTempDatas[t] = default;
                    else
                        mTempDatas = null;
                }
            }
            else
            {
                var t = mSize - mMainDatas.Length;
                var i = index - mMainDatas.Length;
                result = mTempDatas[i];
                if (mSize > mMainDatas.Length)
                {
                    mTempDatas[i] = mTempDatas[t];
                    mTempDatas[t] = default;
                }
                else
                {
                    mTempDatas = null;
                }
            }
            return result;
        }

        public void Clear()
        {
            if (!mIsValue)
            {
                var size = mSize < mMainDatas.Length ? mSize : mMainDatas.Length;
                System.Array.Clear(mMainDatas, 0, size);
                mTempDatas = null;
            }
            mSize = 0;
        }

        public void Cleanup()
        {
            var size = mSize < mMainDatas.Length ? mSize : mMainDatas.Length;
            System.Array.Clear(mMainDatas, 0, size);
            mTempDatas = null;
            mSize = 0;
        }

        public int IndexOf(T item)
        {
            var t0 = mSize < mMainDatas.Length ? mSize : mMainDatas.Length;
            for (int i = 0; i < t0; i++)
            {
                if (mEqual(mMainDatas[i], item))
                    return i;
            }
            var t1 = mSize - t0;
            for (int i = 0; i < t1; i++)
            {
                if (mEqual(mTempDatas[i], item))
                    return i + t0;
            }
            return -1;
        }

        public int FindIndex(T item, int startIndex)
        {
            var t0 = mSize < mMainDatas.Length ? mSize : mMainDatas.Length;
            var t1 = mSize - t0;
            var index = startIndex < mSize && startIndex >= 0 ? startIndex : (mSize - 1);
            if (index < mMainDatas.Length)
            {
                for (int i = index; i >= 0; i--)
                {
                    if (mEqual(mMainDatas[i], item))
                        return i;
                }
                for (int i = t0 - 1; i > index; i--)
                {
                    if (mEqual(mMainDatas[i], item))
                        return i;
                }
                for (int i = 0; i < t1; i++)
                {
                    if (mEqual(mTempDatas[i], item))
                        return i + t0;
                }
            }
            else
            {
                var t = index - t0;
                for (int i = t; i >= 0; i--)
                {
                    if (mEqual(mTempDatas[i], item))
                        return i + t0;
                }
                for (int i = t0 - 1; i >= 0; i--)
                {
                    if (mEqual(mMainDatas[i], item))
                        return i;
                }
                for (int i = t1 - 1; i > t; i--)
                {
                    if (mEqual(mTempDatas[i], item))
                        return i + t0;
                }
            }
            return -1;
        }

        public bool Contains(T item)
        {
            var t0 = mSize < mMainDatas.Length ? mSize : mMainDatas.Length;
            for (int i = 0; i < t0; i++)
            {
                if (mEqual(mMainDatas[i], item))
                    return true;
            }
            var t1 = mSize - t0;
            for (int i = 0; i < t1; i++)
            {
                if (mEqual(mTempDatas[i], item))
                    return true;
            }
            return false;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            var len = ParallelUtils.Min(array.Length - arrayIndex, mSize);
            var t0 = len < mMainDatas.Length ? len : mMainDatas.Length;
            if (t0 > 0)
                System.Array.Copy(mMainDatas, 0, array, arrayIndex, t0);
            var t1 = len - t0;
            if (t1 > 0)
                System.Array.Copy(mTempDatas, 0, array, arrayIndex + t0, t1);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new Enumerator(this);
        }

        public struct Enumerator : IEnumerator<T>
        {
            int index;
            CascadeList<T> lst;
            public T Current { get { return lst[index]; } }

            object IEnumerator.Current { get { return lst[index]; } }

            internal Enumerator(CascadeList<T> lst)
            {
                this.lst = lst;
                index = -1;
            }

            public void Dispose()
            {
                index = -1;
            }

            public bool MoveNext()
            {
                index++;
                return index < lst.Count;
            }

            public void Reset()
            {
                index = -1;
            }
        }
    }
}